﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CoderJoe
{
    public class AspectContainer
    {
        protected List<KeyValuePair<AspectAttribute, IAspect>> Aspects { get; private set; }
        protected AspectCategory Category { get; private set; }

        public AspectContainer(AspectCategory category)
        {
            Category = category;
            Aspects = new List<KeyValuePair<AspectAttribute, IAspect>>();
        }

        private void InvokeBegin()
        {
            var aspects =
                (from a in Aspects
                 where ((a.Key.JoinPoint & AspectJoinPoint.Begin) == AspectJoinPoint.Begin)
                    && a.Value is IBeginAspect
                 orderby a.Key.Order
                 select a.Value);

            foreach (IBeginAspect aspect in aspects)
            {
                aspect.Begin();
            }
        }

        private void InvokeEnd()
        {
            var aspects =
                (from a in Aspects
                 where ((a.Key.JoinPoint & AspectJoinPoint.Begin) == AspectJoinPoint.Begin)
                    && a.Value is IEndAspect
                 orderby a.Key.Order
                 select a.Value);

            foreach (IEndAspect aspect in aspects)
            {
                aspect.End();
            }
        }

        private Action GetInnerWrapper(Action core)
        {
            Action current = core;

            var aspects =
                (from a in Aspects
                 where ((a.Key.JoinPoint & AspectJoinPoint.WrapInner) == AspectJoinPoint.WrapInner)
                    && a.Value is IInnerWrapAspect
                 orderby a.Key.Order
                 select a.Value);

            foreach (IInnerWrapAspect a in aspects)
            {
                current = delegate() { a.WrapInner(current); };
            }

            return current;
        }

        private Action GetOuterWrapper(Action core)
        {
            Action current = delegate()
            {
                this.InvokeBegin();
                this.GetInnerWrapper(core)();
                this.InvokeEnd();
            };

            var aspects =
                (from a in Aspects
                 where ((a.Key.JoinPoint & AspectJoinPoint.WrapOuter) == AspectJoinPoint.WrapOuter)
                    && a.Value is IOuterWrapAspect
                 orderby a.Key.Order
                 select a.Value);

            foreach (IOuterWrapAspect a in aspects)
            {
                current = delegate() { a.WrapOuter(current); };
            }

            return current;
        }

        public void Invoke(Action core)
        {
            GetOuterWrapper(core)();
        }

        internal void FindAspects(object instance)
        {
            Type instanceType = instance.GetType();

            var attrs = 
                (from a in AspectAttribute.GetCustomAttributes(instanceType, typeof(AspectAttribute)).Cast<AspectAttribute>()
                 where ((a.Category & Category) == Category)
                 select a);

            foreach (AspectAttribute attr in attrs)
            {
                KeyValuePair<AspectAttribute, IAspect> kvp = new KeyValuePair<AspectAttribute, IAspect>(
                    attr, attr.CreateInstance());

                kvp.Value.SetContainer(this);

                Aspects.Add(kvp);
            }
        }
    }
}
